// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Author: Markus Gutschke

#include <poll.h>
#include <stdlib.h>
#include <string.h>

#include "base64.h"
#include "check.h"
#include "control_job_status.h"
#include "email_print_job.h"
#include "fetch_job.h"
#include "ijs_backend.h"
#include "misc.h"
#include "output_print_job.h"
#include "printer.h"
#include "proxyloop.h"
#include "xml.h"
#include "xmpp.h"

#define min(a,b) ({ typeof(a) _a=(a); typeof(b) _b=(b); _a < _b ? _a : _b; })
#define max(a,b) ({ typeof(a) _a=(a); typeof(b) _b=(b); _a > _b ? _a : _b; })


static int proxyCallback(struct XML *xml, struct XMLElement *elem, void *arg) {
  struct Printer *printer = (struct Printer *)arg;
  struct XMPP *xmpp = printer->xmpp;
  struct XMLElement *child;
  if (xmlElementMatches(elem, "jabber:client", "message") &&
      xmlElementHasChild(elem, &child, "google:push", "push", NULL) &&
      xmlElementHasChild(child, &child, "google:push", "data", NULL) &&
      child->children_size == 1 && child->children[0]->type == X_CDATA) {
    // Received a new print job notification. Fetch all pending jobs and
    // try to print them.
    char *printerId;
    check(printerId = strdup(child->children[0]->cdata));
    printerId[b64decode(printerId, printerId, strlen(printerId))] = '\000';
    struct JSON *response = fetchJob(printer->cloudPrintURL,
                                     xmpp->oauth2, printerId);
    free(printerId);
    check(response && response->type == J_OBJECT);
    struct JSON *jobs = (struct JSON *)getFromHashMap(&response->obj, "jobs");
    check(jobs && jobs->type == J_ARRAY);
    for (int i = 0; i < jobs->array_size; ++i) {
      check(jobs->array[i].type = J_OBJECT);
      struct JSON *jobid;
      check(jobid = (struct JSON *)getFromHashMap(&jobs->array[i].obj, "id"));
      check(jobid->type == J_STRING);
      controlJobStatus(printer->cloudPrintURL, xmpp->oauth2, jobid->str,
                       S_IN_PROGRESS, 0, NULL);
      if (printer->toEmail) {
        emailPrintJob(xmpp->oauth2, &jobs->array[i],
                      printer->fromEmail ? printer->fromEmail:printer->toEmail,
                      printer->toEmail);
      } else {
        check(printer->printerBackend);
        outputPrintJob(xmpp->oauth2, &jobs->array[i], printer->printerBackend);
      }
      controlJobStatus(printer->cloudPrintURL, xmpp->oauth2,
                       jobid->str, S_DONE, 0, NULL);
    }
    deleteJSON(response);
  }
  return 0;
}

struct Proxy *newProxy(struct Printer *printers, int numPrinters) {
  struct Proxy *proxy;
  check(proxy = malloc(sizeof(struct Proxy)));
  initProxy(proxy, printers, numPrinters);
  return proxy;
}

void initProxy(struct Proxy *proxy, struct Printer *printers, int numPrinters){
  proxy->printers    = printers;
  proxy->numPrinters = numPrinters;
}

void destroyProxy(struct Proxy *proxy) {
  if (proxy) {
    deleteAllPrinters(proxy->printers, proxy->numPrinters);
  }
}

void deleteProxy(struct Proxy *proxy) {
  destroyProxy(proxy);
  free(proxy);
}

void proxyLoop(struct Proxy *proxy) {
  static const unsigned intv = 10*60*1000;
  struct Printer *printers   = proxy->printers;
  int numPrinters            = proxy->numPrinters;
  struct pollfd *pfd;
  check(pfd                  = malloc(numPrinters * sizeof(struct pollfd)));
  time_t tmo                 = 0;
  int tickle                 = 0;
  unsigned long now          = millis();
  for (int i = 0; i < numPrinters; ++i) {
    printers[i].activity     = now;
  }
  for (;;) {
    int action;
    do {
      action = 0;
      for (int i = 0; i < numPrinters; ++i) {
        if (connIsReadable(printers[i].xmpp->conn, 0)) {
          action = 1;
          printers[i].activity = now;
          if (!handleXmppPacket(printers[i].xmpp, proxyCallback, printers+i)) {
            fatal("XMPP communication error");
          }
        } else if (tickle) {
          if (!printers[i].activity) {
            fatal("XMPP connection timed out");
          } else if (now - printers[i].activity >= intv) {
            if (!tickleXmpp(printers[i].xmpp)) {
              printers[i].activity = 0;
            } else {
              fatal("XMPP connection failed");
            }
          }
        }
      }
    } while (action);
    for (int i = 0; i < numPrinters; ++i) {
      pfd[i].fd     = printers[i].xmpp->conn->fd;
      pfd[i].events = POLLIN;
    }
    if (tickle) {
      tmo = now + intv;
    }
    unsigned nxt = min(max(0, tmo - now), intv);
    tmo          = nxt + millis();
    int nfds     = poll(pfd, numPrinters, nxt);
    now          = millis();
    tickle       = !nfds || now >= tmo;
    if (nfds < 0) {
      fatal("Poll() failed");
    }
  }
  free(pfd);
}
